import Botw from "./Botw.js"
import getFaceName from "./FaceMap.js"
import getErrorMsg from "./ErrorMsg.js"
import lodash from 'lodash'
import { FormData, Blob } from "formdata-node"
import fs from "node:fs"
import url from "node:url"
import QRCode from "qrcode"
import buffer from "node:buffer"
import path from "node:path"

export default class Bott extends Botw {
  #configFile

  constructor(config) {
    super({
      appId: config.appId,
      token: config.token
    })

    this.appId = config.appId
    this.token = config.token
    this.sandBox = config.sandBox === true
    this.allMsg = config.allMsg === true
    this.autoStart = config.autoStart === true
    this.ErrorEcho = config.ErrorEcho === true
    this.toQRCode = config.toQRCode === true
    this.config = config
    this.#configFile = config.file
    this.toQRCodeRegExp = false

    if (this.autoStart) this.start()
  }

  get configFile() {
    return this.#configFile
  }

  get name() {
    return this.user?.username || this.appId
  }

  async start() {
    if (this.isStop === false) return

    let config = {
      ...this.config,
      sandBox: this.sandBox,
      intents: 0 | 1 << 1 | 1 << 12,
      updateWssUrl: false,
      reConInterval: 3000
    }

    if (this.allMsg) config.intents |= 1 << 9
    else config.intents |= 1 << 30

    this.init(config, (msg) => {
      this.outLogD("[频道插件] 获取消息", JSON.stringify(msg))

      switch (msg.t) {
        case "GUILD_MEMBER_ADD":
          this.hMemberAdd(msg.d)
          return
        case "GUILD_MEMBER_REMOVE":
          this.hMemberRemove(msg.d)
          return
        case "AT_MESSAGE_CREATE":
        case "MESSAGE_CREATE":
          this.hMsgCreate(msg.d)
          return
        case "PUBLIC_MESSAGE_DELETE":
        case "MESSAGE_DELETE":
          this.hMsgDelete(msg.d)
          return
        case "DIRECT_MESSAGE_CREATE":
          this.hDmsCreate(msg.d)
          return
        case "DIRECT_MESSAGE_DELETE":
          this.hDmsDelete(msg.d)
          return
        default:
          this.outLogW("收到未适配请求：", msg.t, "，请求信息：", JSON.stringify(msg.d))
          return
      }
    })
  }

  async hMemberAdd(msg) {
    this.outLogM("频道.加入成员", msg)
    // TODO ........
  }

  async hMemberRemove(msg) {
    this.outLogM("频道.成员退出", msg)
    // TODO ........
  }

  async hMsgCreate(msg) {
    this.outLogD("子频道.收到消息", msg)
    await this.callPlugs(msg)
  }

  async hMsgDelete(msg) {
    this.outLogD("子频道.撤回消息", msg)
    // TODO ........
  }

  async hDmsCreate(msg) {
    this.outLogD("私信.收到消息", msg)
    await this.callPlugs(msg, true)
  }

  async hDmsDelete(msg) {
    this.outLogD("私信.撤回消息", msg)
    // TODO ........
  }

  async makeQRCode(data) {
    return (await QRCode.toDataURL(data)).replace("data:image/png;base64,", "base64://")
  }

  async ConversionLink(data) {
    const form = new FormData();
    form.append('format', 'url');
    form.append('content', data);
    // form.append('lexer', 'text')
    form.append('expiry_days', '7')
    form.append('expiry_seconds', '0')
    form.append('private', 'false')

    const res = await fetch('https://paste.mozilla.org/api/', {
      method: 'POST',
      body: form
    });
    const url = (await res.text()).trim()
    if (res.status != 200) return false
    return await this.makeQRCode(url)
  }

  async callPlugs(msg, isDms = false) {
    if (typeof this.toQRCode == "boolean")
      this.toQRCodeRegExp = this.toQRCode ? /https?:\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/g : false
    else
      this.toQRCodeRegExp = new RegExp(this.toQRCode, "g")

    try {
      if (typeof msg.content !== "string")
        msg.content = JSON.stringify(msg.content)
    } catch (err) {
      this.outLogW(err)
      msg.content = ""
    }

    let e = await this.makee(msg, isDms)
    if (e) {
      // 累加接受消息数量
      Bot[this.id].stat.recv_msg_cnt++;
      // 输出日志
      logger.info(`recv from plugin: [Guild: ${e.group_name || e.sender.nickname}(${e.group_id || e.sender.user_id}), Member: ${e.sender.nickname}(${e.sender.user_id})] ${e.raw_message}`)
      /*
      // 尝试适配py插件
      Bot[e.group_id || e.user_id] = {
        sendMsg: async (m, q) => {
          let ret
          if (Array.isArray(m)) {
            for (const i of m)
              ret = await e.reply(i, q)
          } else ret = await e.reply(m, q)
          return ret
        },
        makeForwardMsg: async (msg) => {
          if (!Array.isArray(msg)) msg = [msg]
          return msg
        }
      }
      */
      e.bot = Bot[this.id]
      Bot.em(`${e.post_type}.${e.message_type}`, e)
    } else this.outLogE("转制消息失败.")
  }

  async makee(msg, isDms) {
    let e = isDms ? await this.makeePrivate(msg) : await this.makeeGroup(msg)

    e.toString = () => {
      return msg.content
        .replace(new RegExp(`\\<@!${this.id}>`, "g"), `{at:${Bot.uin}}`)
        .replace(/\<emoji:(\d+)>/g, "{face:$1}")
        .replace(/\<@!(\d+)>/g, "{at:$1}")
    }

    /**
    * @param msg 发送的消息
    * @param quote 是否引用回复
    * @param data.recallMsg 群聊是否撤回消息，0-120秒，0不撤回
    * @param data.at 是否at用户
    */
    e.reply = async (m, q, d = {}) => {
      this.outLogD("回复消息.", JSON.stringify(m).replace(/base64:\/\/.*?(,|]|")/g, "base64://...$1"))

      if (m?.type == "xml" || m?.type == "json") {
        this.outLogW("暂时不支持发送xml和json消息")
        return false
      }

      /*
      if (Array.isArray(m))
        for (const i of m)
          if (i?.type == "at") {
            q = true
            m.pop(m.indexOf(i))
          }
      */

      let { recallMsg = 0, at = '' } = d

      if (at && e.isGroup) {
        let text = ''
        if (e?.sender?.card) {
          text = lodash.truncate(e.sender.card, { length: 10 })
        }
        if (at === true)
          at = Number(e.user_id)
        else if (!isNaN(at)) {
          text = e.sender?.nickname
          text = lodash.truncate(text, { length: 10 })
        }

        if (Array.isArray(m))
          m = [segment.at(at, text), ...m]
        else
          m = [segment.at(at, text), m]
      }

      let addImg = async (f) => {
        if (Buffer.isBuffer(f)) return f

        if (typeof f === "string") {
          let p = f

          if (/^file:/i.test(f)) {
            p = f.replace(/^file:\/\//i, "")
            if (!fs.existsSync(p)) {
              p = f.replace(/^file:\/\/\//i, "")
              if (!fs.existsSync(p)) {
                try {
                  p = url.fileURLToPath(f)
                } catch (err) {
                  this.outLogW("转制图片失败(File协议不正确).", f.replace(/base64:\/\/.*?(,|]|")/g, "base64://...$1"), err)
                }
              }
            }
          }

          if (p.match(/^https?:\/\//))
            return Buffer.from(await (await fetch(p)).arrayBuffer())

          if (p.match(/^base64:\/\//))
            return Buffer.from(p.replace(/^base64:\/\//, ""), "base64")

          if (fs.existsSync(p))
            return Buffer.from(fs.readFileSync(p))
        }

        this.outLogW("转制图片失败，未知或未适配协议", (typeof f) == 'object' ? JSON.stringify(f).replace(/base64:\/\/.*?(,|]|")/g, "base64://...$1") : f.toString().replace(/base64:\/\/.*?(,|]|")/g, "base64://...$1"))
      }

      let addMsg = async (m) => {
        switch (typeof m) {
          case "string":
            return m
          case "number":
            return `${m}`
          case "object":
            switch (m.type) {
              case "text":
                return m.text
              case "face":
                return `<emoji:${m.id}>`
              case "at":
                if (m.id) return `<@!${m.id}>`
                if (m.qq == "all") return "@everyone"
                return m.qq == parseInt(e.user_id) ? `<@!${e.user_id}>` : m.text
              case "image":
                return await addImg(m.file)
              default:
                if (!Array.isArray(m)) {
                  this.outLogW("暂未支持的消息", JSON.stringify(m))
                  return
                }
            }
            break
          default:
            this.outLogW("暂未支持的消息", JSON.stringify(m))
            return
        }

        let msgX = []

        let msgY = {
          content: "",
          images: []
        }

        for (let v of m) {
          let x = await addMsg(v)
          switch (typeof x) {
            case "string":
              const match = x.match(this.toQRCodeRegExp)
              if (match && this.toQRCodeRegExp) {
                for (const i of match) {
                  msgY.images.push(await addImg(await this.makeQRCode(i)))
                  x = x.replace(i, "[链接(请扫码查看)]")
                }
              }
              msgY.content += x
              continue
            case "object":
              if (Buffer.isBuffer(x)) msgY.images.push(x)
              else msgX = [...msgX, ...x]
              continue
            default:
              continue
          }
        }

        if (msgY.content.length || msgY.images.length) msgX = [msgY, ...msgX]
        if (msgY.content.length && msgY.images.length) q = false

        return msgX
      }

      let send = async (m) => {
        m.msg_id = msg.id
        if (m.content) {
          m.content = m.content.trim()
          if (q) {
            m.message_reference = {
              message_id: msg.id,
              ignore_get_message_error: false
            }
          }
        }
        m.channel_id = msg.channel_id
        m.guild_id = msg.guild_id
        return await this.postMsg(isDms ? msg.guild_id : msg.channel_id, m, isDms)
      }

      let sendMsg = async (m) => {
        if (!m || m.length == 0) return { "code": 50006, "message": "CannotSendEmptyMessage" }
        switch (typeof m) {
          case "string":
            if (/^base64:\/\//.test(m)) return await send({ file_image: m })
            return await send({ content: m })
          case "object":
            if (Buffer.isBuffer(m)) return await send({ file_image: m })
          default:
            break
        }

        let rsp

        for (let v of m) {
          let n = {}

          if (v.content.length) n.content = v.content

          if (v.images.length === 1) n.file_image = v.images[0]

          rsp = await send(n)

          if (v.images.length > 1) {
            this.outLogD("开始回复图片. 共", v.images.length, "张")
            for (let i in v.images) {
              this.outLogD("回复图片. 第", parseInt(i) + 1, "张")
              rsp = await send({ file_image: v.images[i] })
            }
            this.outLogD("回复图片完成.")
          }
        }

        return rsp
      }

      let x = await addMsg(m)

      this.outLogD("回复消息转制结果.", x)

      let rsp = await sendMsg(x)

      this.outLogD("回复消息结果.", rsp)

      // 判断一下是不是频道
      if (e.guild_id && recallMsg > 0 && rsp?.seq_in_channel)
        if (e.isGroup) setTimeout(() => e.group.recallMsg(rsp.id), recallMsg * 1000)

      if (rsp?.seq_in_channel) {
        logger.info(`succeed to send: [Channel(${rsp.guild_id}), Guild(${rsp.channel_id})] ${x[0].content}${"[图片]".repeat(x[0].images?.length || 0)}`)
        // 累加发送消息数量
        Bot[this.id].stat.sent_msg_cnt++;
        return {
          seq: rsp.seq_in_channel,
          rand: 10000000,
          time: parseInt(Date.parse(rsp.timestamp) / 1000),
          message_id: rsp.id
        }
      } else if (rsp?.message || rsp?.code) {
        this.saveErr("发送消息", { appId: this.appId, channel_id: msg.channel_id, guild_id: msg.guild_id, rsp })
        let err = getErrorMsg(rsp.code)
        if (err) rsp.message = err
        logger.error(`failed to send: [Channel(${e.guild_id}), Guild(${e.channel_id})] ${rsp.message}(${rsp.code})`)

        if (rsp.code == 304003) {
          let ret = [`上一条消息包含链接，已自动转换成二维码`]
          let x3 = []
          for (const i of x) x3.push(i?.content || i)
          let cl = await this.ConversionLink(x3.join("\n"))
          if (cl) ret.push(segment.image(cl))
          else ret.push("转换失败")
          let x2 = await addMsg(ret)
          this.outLogD("内容转换结果.", x2)
          let rsp2 = await sendMsg(x2)
          this.outLogD("内容转换发送结果.", rsp2)
          if (rsp2?.seq_in_channel) {
            this.outLogN("内容转换发送成功")
            Bot.stat.sent_msg_cnt++;
            return {
              seq: rsp2.seq_in_channel,
              rand: 10000000,
              time: parseInt(Date.parse(rsp2.timestamp) / 1000),
              message_id: rsp2.id
            }
          }
          else if (rsp2?.message || rsp2?.code) this.outLogE(`内容转换发送失败：${getErrorMsg(rsp2.code) || rsp2.message}(${rsp2.code})`)
          else this.outLogE(`内容转换发送失败：${rsp2}`)
        } else if (this.ErrorEcho) {
          let x2 = await addMsg(`上一条消息发送失败：${rsp.message}(${rsp.code})`)
          let rsp2 = await sendMsg(x2)
          this.outLogD("错误回显发送结果.", rsp2)
          if (rsp2?.seq_in_channel) this.outLogN("错误回显发送成功")
          else if (rsp2?.message || rsp2?.code) this.outLogE(`错误回显发送失败：${getErrorMsg(rsp2.code) || rsp2.message}(${rsp2.code})`)
          else this.outLogE(`错误回显发送失败：${rsp2}`)
        }
      } else {
        this.saveErr("发送消息", { appId: this.appId, channel_id: msg.channel_id, guild_id: msg.guild_id, rsp })
        logger.error(`failed to send: [Channel(${e.guild_id}), Guild(${e.channel_id})] ${rsp}`)
        if (this.ErrorEcho) {
          let x2 = await addMsg(`上一条消息发送失败：${rsp || "发送返回空"}`)
          let rsp2 = await sendMsg(x2)
          this.outLogD("错误回显结果.", rsp2)
          if (rsp2?.seq_in_channel) this.outLogN("错误回显发送成功")
          else if (rsp2?.message || rsp2?.code) this.outLogE(`错误回显发送失败：${getErrorMsg(rsp2.code) || rsp2.message}(${rsp2.code})`)
          else this.outLogE(`错误回显发送失败：${rsp2 || "发送返回为空"}`)
        }
      }
      return false
    }

    e.callApi = async (url, option = {}) => {
      option["channel_info"] = { channel_id: e.channel_id, guild_id: e.guild_id }
      this.outLogN("手动请求数据url：", url, "请求头：", option)
      let ret = await this.callApi(url, option)
      this.outLogE("返回结果：", JSON.stringify(ret))
      return ret
    }

    this.outLogD("转制消息.", e)
    return e
  }

  async makeePrivate(msg) {
    let time = parseInt(Date.parse(msg.timestamp) / 1000)
    let message = this.makeeMessage(msg)
    return {
      ...message,
      post_type: "message",
      message_id: msg.id,
      user_id: msg.author.id,
      time,
      message_type: "private",
      sub_type: "group",
      sender: {
        avatar: msg.author.avatar,
        user_id: msg.author.id,
        nickname: msg.author.username,
        group_id: msg.src_guild_id,
        discuss_id: undefined
      },
      from_id: msg.author.id,
      to_id: this.id,
      font: "宋体",
      seq: msg.seq,
      rand: 10000000,
      friend: {
        poke: () => { this.outLogN(`戳 ${msg.author.id}`) }
      },
      auto_reply: false,
      self_id: this.id
    }
  }

  async makeeGroup(msg) {
    let time = parseInt(Date.parse(msg.timestamp) / 1000)
    let role = msg.member.roles.includes("4") ? "owner" : msg.member.roles.includes("2") ? "admin" : "member"

    let info = await this.ChannelInfo(msg.channel_id, msg.guild_id)
    this.outLogD("获取子频道信息", info)

    let message = this.makeeMessage(msg)
    let atme = false

    if (Array.isArray(msg.mentions)) for (let at of msg.mentions) {
      if (at.id === this.id) {
        atme = true
        break
      }
    }

    let member = {
      info: {
        group_id: msg.channel_id,
        user_id: msg.author.id,
        nickname: msg.author.username,
        card: msg.author.username,
        sex: "unknown",
        age: 100,
        area: "",
        level: 100,
        rank: "",
        role,
        title: "",
        title_expire_time: 0,
        shutup_time: 0,
        update_time: 0,
        join_time: Date.parse(msg.member.joined_at) / 1000,
        last_sent_time: time
      },
      // group_id: msg.guild_id,
      group_id: msg.channel_id,
      is_admin: role === "owner" || role === "admin",
      is_owner: role === "owner",
      getAvatarUrl: () => {
        return msg.author.avatar
      }
    }

    return {
      ...message,
      post_type: "message",
      message_id: msg.id,
      user_id: msg.author.id,
      time,
      message_type: "group",
      sub_type: "normal",
      sender: {
        user_id: msg.author.id,
        nickname: msg.author.username,
        card: msg.author.username,
        sex: "unknown",
        area: "",
        title: "",
        level: 100,
        age: 100,
        role
      },
      block: false,
      anonymous: null,
      atall: msg.mention_everyone === true,
      // group_id: msg.guild_id,
      group_id: msg.channel_id,
      channel_id: msg.channel_id,
      guild_id: msg.guild_id,
      group_name: info.name || "频道插件",
      self_id: this.id,
      font: "宋体",
      seq: msg.seq,
      rand: 10000000,
      atme,
      member,
      group: {
        pickMember: (id) => {
          if (id === msg.author.id) {
            return member
          }
        },
        recallMsg: (msg_id) => {
          this.delChannelMsg(msg.channel_id, msg.guild_id, msg_id, false)
        },
        pokeMember: (qq) => {
          this.outLogN(`戳 ${qq}`)
        },
        muteMember: (qq, s) => {
          this.outLogN(`禁言 ${qq} ${s}秒`)
        },
        makeForwardMsg: (msg) => {
          return msg
        }
      },
      recall: () => {
        this.delChannelMsg(msg.channel_id, msg.guild_id, msg_id, false)
      }
    }
  }

  makeeMessage(msg) {
    let message = []
    let raw_message = ""
    // 处理前缀
    msg.content = msg.content.replace(/^( |)\//, "#")
    let rex_message = msg.content
    let rex = /(?<t>\<((?<at>@!)|(?<face>emoji):)(?<id>\d+)>)/

    let getAtName = (i) => {
      for (let n of msg.mentions) if (n.id === i) return `@${n.username}`
      return `@${i}`
    }

    if (msg.message_reference?.message_id) {
      message.push({ type: "reply", id: msg.message_reference.message_id })
      raw_message += `[回复：${msg.message_reference.message_id}] `
    }

    raw_message += msg.content

    while (rex) {
      let r = rex.exec(rex_message)

      if (!r) {
        if (rex_message.length) message.push({ type: "text", text: rex_message.replace(/^( |)\//, "#").trim() })
        break
      }

      if (r.index)
        message.push({ type: "text", text: rex_message.slice(0, r.index).replace(/^( |)\//, "#").trim() })

      rex_message = rex_message.slice(r.index + r.groups.t.length)

      if (r.groups.at) {
        let qq = r.groups.id
        let text = getAtName(r.groups.id)
        if (r.groups.id === this.id) qq = this.id
        raw_message = raw_message.replace(r.groups.t, `{at:${r.groups.id}}`)
        message.push({ type: "at", qq, text })
        continue
      }

      if (r.groups.face) {
        let text = getFaceName(r.groups.id)
        raw_message = raw_message.replace(r.groups.t, `{face:${r.groups.id},text:/${text}}`)
        message.push({ type: "face", id: r.groups.id, text })
        continue
      }
    }

    if (msg.attachments) for (const i of msg.attachments) {
      i.filename = i.filename.replace(/{|}/g, "").replace(/-/g, "")
      i.type = i.content_type.split("/")[0]
      i.file = i.filename
      if (i.url)
        i.url = `${!i.url.match(/^https?:\/\//) ? "http://" : ""}${i.url}`
      message.push(i)
      raw_message += `{${i.type}: ${i.file}}`
    }

    return { message, raw_message }
  }

  async postMsg(toId, msg, isDms) {
    let _url = isDms ? `/dms/${toId}/messages` : `/channels/${toId}/messages`
    let _option = { method: "POST", channel_info: { channel_id: msg.channel_id, guild_id: msg.guild_id } }
    delete msg.channel_id
    delete msg.guild_id

    if (msg.file_image || msg.embed || msg.ark || msg.file || msg.markdown) {
      _option.body = new FormData()

      if (msg.content) {
        _option.body.append("content", msg.content)
      }
      if (msg.embed) {
        _option.body.append("embed", JSON.stringify(msg.embed))
      }
      if (msg.ark) {
        _option.body.append("ark", JSON.stringify(msg.ark))
      }
      if (msg.message_reference) {
        _option.body.append("message_reference", JSON.stringify(msg.message_reference))
      }
      if (msg.image) {
        _option.body.append("image", msg.image)
      }
      if (msg.msg_id) {
        _option.body.append("msg_id", msg.msg_id)
      }
      if (msg.event_id) {
        _option.body.append("event_id", msg.event_id)
      }
      if (msg.markdown) {
        _option.body.append("markdown", JSON.stringify(msg.markdown))
      }
      if (msg.file_image) {
        if (Buffer.isBuffer(msg.file_image))
          _option.body.append("file_image", new Blob([msg.file_image]))
        else if (fs.existsSync(msg.file_image)) {
          let buf = fs.readFileSync(msg.file_image)
          _option.body.append("file_image", new buffer.File([buf.parent], path.basename(msg.file_image)))
        }
      }
      if (msg.file) {
        _option.body.append("file_image", new buffer.File([msg.file.parent], "image"))
      }
    } else {
      _option.headers = { "Content-Type": "application/json" }
      _option.body = JSON.stringify(msg)
    }

    let log = typeof _option.body === "object" ? [ "Body：", JSON.stringify(Array.from(_option.body)).replace(/base64:\/\/.*?(,|]|")/g, "base64://...$1") ] : []
    this.outLogD("请求数据", JSON.stringify(_option).replace(/base64:\/\/.*?(,|]|")/g, "base64://...$1"), ...log)

    return await this.callApi(_url, _option)
  }

  async delChannelMsg(channel_id, guild_id, message_id, hidetip) {
    let _url = `/channels/${channel_id}/messages/${message_id}?hidetip=${hidetip === true}`
    let _option = { method: "DELETE", channel_info: { channel_id: channel_id, guild_id: guild_id } }
    return await this.callApi(_url, _option)
  }

  async ChannelInfo(channel_id, guild_id) {
    let _url = `/channels/${channel_id}`
    let _option = { method: "GET", channel_info: { channel_id: channel_id, guild_id: guild_id } }
    return await this.callApi(_url, _option)
  }
}
